package com.iagent.request.apache;

import com.iagent.bean.IagentFile;
import com.iagent.bean.RequestParameter;
import com.iagent.constant.HttpConstant;
import com.iagent.exception.HttpExecutorException;
import com.iagent.json.JSON;
import com.iagent.logging.LogFactory;
import com.iagent.logging.Logger;
import com.iagent.request.AbstractHttpExecutor;
import com.iagent.util.CollectionUtils;
import com.iagent.util.RandomUtils;
import com.iagent.util.ReflectUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author liujieyu
 * @date 2022/12/14 13:57
 * @desciption This is abstract for Apache Http Client
 */
public abstract class AbstractApacheHttpExecutor extends AbstractHttpExecutor {

    private static final Logger logger = LogFactory.getLogger(AbstractApacheHttpExecutor.class);
    /**
     * Get Http Client
     * @param url
     * @param config
     * @return
     * @throws UnsupportedOperationException
     */
    public abstract CloseableHttpClient getHttpClient(String url, com.iagent.request.RequestConfig config) throws UnsupportedOperationException;

    /**
     * release http client
     *
     * @param httpClient
     * @throws UnsupportedOperationException
     */
    public abstract void releaseConnection(CloseableHttpClient httpClient) throws UnsupportedOperationException;

    @Override
    public byte[] execute(String url, com.iagent.request.RequestConfig config, RequestParameter data) throws Exception {
        CloseableHttpClient httpClient = getHttpClient(url, config);
        if (logger.isDebugEnabled()) {
            logger.debug("The request url is " + url);
            logger.debug("The request config is " + config);
        }
        HttpRequestBase httpRequest = getHttpRequest(config, url, data);
        CloseableHttpResponse execute = httpClient.execute(httpRequest);
        byte[] response;
        if (execute.getStatusLine().getStatusCode() == 200) {
            if (logger.isDebugEnabled()) {
                logger.debug("The url [" + url + "] execute success");
            }
            response = EntityUtils.toByteArray(execute.getEntity());
            EntityUtils.consume(execute.getEntity());
            httpRequest.releaseConnection();
            releaseConnection(httpClient);
            return response;
        } else {
            httpRequest.releaseConnection();
            releaseConnection(httpClient);
            String msg = "The apache client http execute error, status:" + execute.getStatusLine();
            logger.error(msg);
            throw new HttpExecutorException(msg);
        }
    }

    /**
     * 获取到http 请求，优先处理GET和DELETE请求中的参数，跟在URL上面
     *
     * @param requestConfig 请求配置
     * @param url 请求url
     * @param parameter 表单数据
     * @return
     */
    private HttpRequestBase getHttpRequest(com.iagent.request.RequestConfig requestConfig, String url, RequestParameter parameter) {
        HttpRequestBase httpRequestBase;
        switch (requestConfig.getRequestType()) {
            case POST:
                // 非表单类型，需将@ParamKey放到url上
                if (!HttpConstant.MULTIPART_FORM_DATA_VALUE.equals(requestConfig.getContentType())) {
                    url = handleUrl(parameter.getForm(), url);
                    httpRequestBase = new HttpPost(url);
                    handleBody(httpRequestBase, parameter.getBody(), requestConfig.getContentType());
                } else {
                    httpRequestBase = new HttpPost(url);
                    handleMultiPartForm(httpRequestBase, parameter);
                }
                break;
            case PUT:
                if (!HttpConstant.MULTIPART_FORM_DATA_VALUE.equals(requestConfig.getContentType())) {
                    url = handleUrl(parameter.getForm(), url);
                    httpRequestBase = new HttpPut(url);
                    handleBody(httpRequestBase, parameter.getBody(), requestConfig.getContentType());
                } else {
                    httpRequestBase = new HttpPut(url);
                    handleMultiPartForm(httpRequestBase, parameter);
                }
                break;
            case DELETE:
                url = handleUrl(parameter.getForm(), url);
                httpRequestBase = new HttpDelete(url);
                break;
            case GET:
                // 是默认的请求方式一致
            default:
                // 默认使用 Get 请求
                url = handleUrl(parameter.getForm(), url);
                httpRequestBase = new HttpGet(url);
        }
        handleHeader(httpRequestBase, parameter);
        return httpRequestBase;
    }

    /**
     * 将路径参数封装到url上
     *
     * @param form
     * @param url
     * @return
     */
    private String handleUrl(Map<String, Object> form, String url) {
        StringBuilder stringBuilder = null;
        if (form != null && !form.isEmpty()) {
            stringBuilder = new StringBuilder();
            for (Map.Entry<String, Object> entry : form.entrySet()) {
                String name = entry.getKey();
                String value = String.valueOf(entry.getValue());
                if (stringBuilder.length() != 0) {
                    stringBuilder.append("&");
                }
                stringBuilder.append(name);
                stringBuilder.append("=");
                stringBuilder.append(value);
            }
        }
        if (null == stringBuilder) {
            return url;
        } else {
            String params = stringBuilder.toString();
            return url + "?" + params;
        }
    }

    /**
     * 处理请求头参数
     *
     */
    private void handleHeader(HttpRequestBase httpRequestBase, RequestParameter parameter) {
        if (parameter.getHeaders() != null) {
            for (Map.Entry<String, Object> entry : parameter.getHeaders().entrySet()) {
                String headerName = entry.getKey();
                httpRequestBase.setHeader(headerName, String.valueOf(entry.getValue()));
            }
        }
    }

    /**
     * 处理表单类型请求数据
     */
    private void handleMultiPartForm(HttpRequestBase httpRequestBase, RequestParameter parameter) {
        HttpEntity httpEntity = null;
        if (existsFileType(parameter)) {
            // 使用表单实体提交
            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
            // 普通参数添加
            for (Map.Entry<String, Object> entry : parameter.getForm().entrySet()) {
                // parameter name
                String key = entry.getKey();
                // value
                Object value = entry.getValue();
                if (value instanceof File) {
                    multipartEntityBuilder.addBinaryBody(key, (File) value);
                } else if (value instanceof InputStream) {
                    multipartEntityBuilder.addBinaryBody(key, (InputStream) value, ContentType.DEFAULT_BINARY, RandomUtils.getId());
                } else if (value instanceof byte[]) {
                    multipartEntityBuilder.addBinaryBody(key, (byte[]) value, ContentType.DEFAULT_BINARY, RandomUtils.getId());
                } else if (value instanceof IagentFile){
                    IagentFile iagentFile = (IagentFile) value;
                    String fileName = iagentFile.getName() == null ? RandomUtils.getId() : iagentFile.getName();
                    // handle byte array
                    if (CollectionUtils.isNotEmpty(iagentFile.getContent())) {
                        for (byte[] bytes : iagentFile.getContent()) {
                            multipartEntityBuilder.addBinaryBody(key, bytes, ContentType.DEFAULT_BINARY, fileName);
                        }
                    }
                    // handle inputStream array
                    if (CollectionUtils.isNotEmpty(iagentFile.getInputStream())) {
                        for (InputStream inputStream : iagentFile.getInputStream()) {
                            multipartEntityBuilder.addBinaryBody(key, inputStream, ContentType.DEFAULT_BINARY, fileName);
                        }
                    }
                    // handle file array
                    if (CollectionUtils.isNotEmpty(iagentFile.getFile())) {
                        for (File file : iagentFile.getFile()) {
                            multipartEntityBuilder.addBinaryBody(key, file);
                        }
                    }
                } else {
                    multipartEntityBuilder.addTextBody(key, String.valueOf(value),
                            ContentType.create("text/plain", Charset.forName("UTF-8")));
                }
            }
            // 防止乱码
            multipartEntityBuilder.setCharset(StandardCharsets.UTF_8);
            multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
            httpEntity = multipartEntityBuilder.build();
        } else {
            List<NameValuePair> list = new ArrayList<>();
            for (Map.Entry<String, Object> entry : parameter.getForm().entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                BasicNameValuePair basicNameValuePair = new BasicNameValuePair(key, String.valueOf(value));
                list.add(basicNameValuePair);
            }
            try {
                httpEntity = new UrlEncodedFormEntity(list);
            } catch (UnsupportedEncodingException e) {
                logger.error("Unsupported Encoding Error Info:" + e.getMessage(), e);
                throw new IllegalArgumentException("Unsupported Encoding Error Info:" + e.getMessage());
            }
        }
        if (httpEntity != null) {
            ((HttpEntityEnclosingRequestBase) httpRequestBase).setEntity(httpEntity);
        }
    }

    /**
     * 处理body
     *
     * @param httpRequestBase
     * @param body
     */
    private void handleBody(HttpRequestBase httpRequestBase, Object body, String contentType) {
        if (HttpConstant.MULTIPART_FORM_DATA_VALUE.equals(contentType)) {
            // 表单类型
            Map<String, Object> objectMap = ReflectUtils.getKeyValueByObject(body, false);
            List<NameValuePair> list = new ArrayList<>();
            for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
                BasicNameValuePair basicNameValuePair = new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue()));
                list.add(basicNameValuePair);
            }
            try {
                if (CollectionUtils.isNotEmpty(list)) {
                    ((HttpEntityEnclosingRequestBase) httpRequestBase).setEntity(new UrlEncodedFormEntity(list));
                }
            } catch (UnsupportedEncodingException e) {
                logger.error("Unsupported encoding error info:" + e.getMessage(), e);
                throw new IllegalArgumentException("Unsupported encoding error info:" + e.getMessage());
            }
        } else {
            String bodyString = JSON.toJSONString(body);
            StringEntity bodyEntity = new StringEntity(bodyString, ContentType.create(contentType, Consts.UTF_8));
            // 非表单类型
            if (httpRequestBase instanceof HttpPost) {
                // HttpPost 对象
                ((HttpPost) httpRequestBase).setEntity(bodyEntity);
            } else if (httpRequestBase instanceof HttpPut) {
                // HttpPut 对象
                ((HttpPut) httpRequestBase).setEntity(bodyEntity);
            } else {
                throw new IllegalArgumentException("Request type is Error, body parameter must be Post or Put!");
            }
        }
    }

    /**
     * get ssl socket factory
     *
     * @return
     */
    protected SSLConnectionSocketFactory getSslSocketFactory() {
        SSLContext sslContext;
        try {
            sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();
            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
            return sslSocketFactory;
        } catch (Exception e) {
            logger.error("get ssl socket connection factory error", e);
            return null;
        }
    }

}
